package gm.server;

import gm.GmLog;
import gm.commands.Command;
import gm.commands.CommandModule;
import gm.commands.StandardCommands;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Gobelin készítő szervere.
 *
 * @author imruf84
 */
public abstract class Server extends Thread {

    /**
     * A szerver fut.
     */
    private final AtomicBoolean isRunning;
    /**
     * Port.
     */
    private final int port;
    /**
     * Parancs modul.
     */
    private CommandModule module;
    /**
     * Gép.
     */
    protected Machine machine;

    /**
     * Konstruktor.
     *
     * @param port port
     * @param machine gép
     */
    public Server(int port, final Machine machine) {
        this.isRunning = new AtomicBoolean(true);
        this.port = port;

        // Értelmező létrehozása.
        createCommands();
        this.machine = machine;
    }

    /**
     * Gép lekérdezése.
     *
     * @return gép
     */
    public Machine getMachine() {
        return machine;
    }

    /**
     * Kilépés eseménye.
     */
    abstract protected void onExit();

    /**
     * Parancsok létrehozása.
     */
    private void createCommands() {
        module = new CommandModule();
        module.addCommandSet(new StandardCommands(this));
        // Egyéb rendszerspecifikus parancsok létrehozása.
        createCommandModule();
    }

    /**
     * Parancs modul létrehozása.
     */
    abstract protected void createCommandModule();

    /**
     * Szerver indítása.
     */
    @Override
    public void run() {

        // Addig fut a szerver, amíg ez igaz.
        isRunning.set(true);

        // Szerver indítása.
        try (ServerSocket serverSocket = new ServerSocket(port)) {

            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                try {
                    // Szerver leállítása.
                    serverSocket.close();
                    System.out.println("Server stopped.");
                } catch (IOException ex) {
                    GmLog.printError(ex);
                }
            }));

            serverSocket.setSoTimeout(0);
            System.out.println("Server is listening on port " + serverSocket.getLocalPort() + "...");

            // Mehet a munka.
            while (isRunning.get()) {
                // Kliensek fogadása.
                try (Socket server = serverSocket.accept()) {

                    // Parancs fogadása.
                    BufferedReader fromClient = new BufferedReader(new InputStreamReader(server.getInputStream()));
                    String line = fromClient.readLine();

                    // Válasz küldése.
                    PrintWriter toClient = new PrintWriter(server.getOutputStream(), true);

                    // Szerver leállítása parancs.
                    boolean stop = line.equalsIgnoreCase("stop");
                    String result = stop ? "Server stopping..." : module.execute(line, toClient);

                    toClient.println(result);

                    // Kapcsolat bontása.
                    server.close();

                    if (stop) {
                        onExit();
                        isRunning.set(false);
                    }
                }
            }

        } catch (UnknownHostException e) {
            GmLog.printError(e);
        } catch (IOException e) {
            GmLog.printError(e);
        }
    }

    /**
     * Parancsmodul lekérdezése.
     *
     * @return parancsmodul
     */
    public CommandModule getCommandModule() {
        return module;
    }

    /**
     * Parancslista lekérdezése.
     *
     * @return parancsok listája
     */
    public String getCommandsList() {

        ArrayList<String> cl = new ArrayList<>();
        cl.add("stop");
        cl.add("exit");
        module.values().stream().forEach((c) -> {
            cl.add(c.getName());
        });
        Collections.sort(cl);

        String s = "";
        s = cl.stream().map((c) -> " " + c).reduce(s, String::concat);

        return s;
    }

}
